﻿// ===============================================================================
//  Copyright © NewSQL.CN. All rights reserved .
//  技术论坛：http://newsql.cn/
//  个人博客：http://www.ymind.net/
//  版权所有：NewSQL中文社区 彦铭工作室
// ===============================================================================

using System;
using System.Collections.Generic;
using System.Linq;
using MongoDB.Bson;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using MongoDB.Driver.Wrappers;

namespace Newsql.Data.MongoDB
{
	/// <summary>
	/// 为实体对象基于 MongoDB 的数据访问层提供基类。
	/// </summary>
	/// <typeparam name="T">表示实体对象的类型。</typeparam>
	/// <typeparam name="TId">表示唯一标志符的类型。</typeparam>
	public abstract class MongoDBDALBase<T, TId> : DatabaseDALBase where T : IDBObject<TId>
	                                                               where TId : BsonValue
	{
		private MongoServer _mongoServer;

		/// <summary>
		/// 初始化 <see cref="MongoDBDALBase{T, TId}"/> 对象。
		/// </summary>
		/// <param name="masterGroup">连接字符串。</param>
		protected MongoDBDALBase(string masterGroup) : this(masterGroup, masterGroup) { }

		/// <summary>
		/// 初始化 <see cref="MongoDBDALBase{T, TId}"/> 对象。
		/// </summary>
		/// <param name="masterGroup">主机连接字符串的分组名称。</param>
		/// <param name="slaveGroup">从机连接字符串的分组名称。</param>
		protected MongoDBDALBase(string masterGroup, string slaveGroup) : base("MongoDB", masterGroup, slaveGroup) { }

		/// <summary>
		/// 获取默认数据集名称。
		/// </summary>
		protected abstract string CollectionName { get; }

		/// <summary>
		/// 返回数据库服务器。
		/// </summary>
		/// <param name="master">是否返回主机数据库的实例。</param>
		/// <returns>返回 <see cref="MongoServer"/>。</returns>
		protected MongoServer GetMongoServer(bool master = true) { return this._mongoServer ?? (this._mongoServer = MongoServer.Create(master ? this.MasterConnectionStrings : this.SlaveConnectionStrings)); }

		/// <summary>
		/// 返回数据库的实例。
		/// </summary>
		/// <param name="master">是否返回主机数据库的实例。</param>
		/// <param name="customDatabaseName">自定义片键。</param>
		/// <returns>返回 <see cref="MongoDatabase"/>。</returns>
		protected virtual MongoDatabase GetDatabase(bool master = true, string customDatabaseName = null)
		{
			var databaseName = String.IsNullOrWhiteSpace(customDatabaseName) ? this.DatabaseName : customDatabaseName;
			var server = this.GetMongoServer(master);

			server.Connect();

			return server.GetDatabase(databaseName);
		}

		/// <summary>
		/// 返回数据集的实例。
		/// </summary>
		/// <param name="customCollectionName">自定义数据集名称。</param>
		/// <returns>返回 <see cref="MongoCollection"/>。</returns>
		protected virtual string GetCollectionName(string customCollectionName = null) { return String.IsNullOrWhiteSpace(customCollectionName) ? this.CollectionName : customCollectionName; }

		/// <summary>
		/// 返回数据库中所有项的总数。
		/// </summary>
		/// <returns>返回 <see cref="System.Int64"/>。</returns>
		public long GetTotalCount()
		{
			var database = this.GetDatabase();
			var collectionName = this.GetCollectionName();
			var collection = database.GetCollection(collectionName);

			return collection.Count();
		}

		/// <summary>
		/// 返回一个可用的对象唯一标识符。
		/// </summary>
		/// <returns><typeparamref name="TId"/>，一个可用的对象唯一标识符。</returns>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public TId GetNewId()
		{
			/*
				db.foo.findAndModify({
					query: {"_id": "users"},
					update: {$inc: {"seq":1}},
					new: true
				});
			  
				var document = database.GetCollection(AutoIncrementCollectionName);

				var resutl = document.FindAndModify(Query.EQ("_id", this.DefaultCollectionName), SortBy.Null, global::MongoDB.Driver.Builders.Update.Inc("Index", 1), true, true);
				var newId = resutl.ModifiedDocument["Index"] as T;

				return newId;
			 */

			var database = this.GetDatabase();

			return this.GetNewId(database);
		}

		/// <summary>
		/// 返回新的唯一标识符。
		/// </summary>
		/// <param name="database">保存唯一标识符的数据库对象。</param>
		/// <returns>返回表示唯一标识符的 <see cref="BsonValue"/>。</returns>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		protected abstract TId GetNewId(MongoDatabase database);


		/// <summary>
		/// 返回所有 <typeparamref name="T"/> 对象的集合。
		/// </summary>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public IEnumerable<T> GetAllItems()
		{
			var database = this.GetDatabase(false);
			var document = database.GetCollection<T>(this.CollectionName);

			return document.FindAll();
		}

		/// <summary>
		/// 返回具有指定唯一标识符的 <typeparamref name="T"/> 对象。
		/// </summary>
		/// <param name="id">要返回的 <typeparamref name="T"/> 对象的唯一标识符。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public T GetItemById(TId id)
		{
			if (id == null) throw new ArgumentOutOfRangeException("id");

			var database = this.GetDatabase();
			var document = database.GetCollection<T>(this.CollectionName);

			return document.FindOne(Query.EQ("_id", id));
		}

		/// <summary>
		/// 向数据库添加一个 <typeparamref name="T"/> 对象。
		/// </summary>
		/// <param name="item">要添加的 <typeparamref name="T"/> 对象。</param>
		/// <param name="safeMode">指定安全操作模式。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public SafeModeResult AddNew(T item, SafeMode safeMode = null)
		{
			if (safeMode == null) safeMode = SafeMode.False;

			var database = this.GetDatabase();
			var document = database.GetCollection<T>(this.CollectionName);

			return document.Insert(item, safeMode);
		}

		/// <summary>
		/// 向数据库添加一个 <typeparamref name="T"/> 对象的集合。
		/// </summary>
		/// <param name="items">要添加的 <typeparamref name="T"/> 对象的集合。</param>
		/// <param name="safeMode">指定安全操作模式。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public IEnumerable<SafeModeResult> AddNew(IEnumerable<T> items, SafeMode safeMode = null)
		{
			if (safeMode == null) safeMode = SafeMode.False;

			if (items == null) throw new ArgumentNullException("items");

			var database = this.GetDatabase();
			var collection = database.GetCollection<T>(this.CollectionName);
			var options = new MongoInsertOptions {
				Flags = InsertFlags.ContinueOnError,
				SafeMode = safeMode,
				CheckElementNames = false
			};

			try
			{
				return collection.InsertBatch(items, options);
			}
			catch (MongoSafeModeException)
			{
				return null;
			}
		}

		/// <summary>
		/// 从数据库删除所有对象。
		/// </summary>
		/// <param name="safeMode">指定安全操作模式。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public SafeModeResult RemoveAll(SafeMode safeMode = null)
		{
			if (safeMode == null) safeMode = SafeMode.False;

			var database = this.GetDatabase();
			var collectionName = this.GetCollectionName();
			var collection = database.GetCollection(collectionName);

			return collection.RemoveAll(safeMode);
		}

		/// <summary>
		/// 从数据库删除具有指定唯一标识符的对象。
		/// </summary>
		/// <param name="id">要删除的对象的唯一标识符。</param>
		/// <param name="safeMode">指定安全操作模式。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public SafeModeResult Remove(TId id, SafeMode safeMode = null)
		{
			if (safeMode == null) safeMode = SafeMode.False;

			var database = this.GetDatabase();
			var collectionName = this.GetCollectionName();
			var collection = database.GetCollection(collectionName);

			return collection.Remove(Query.EQ("_id", id), safeMode);
		}

		/// <summary>
		/// 从数据库删除指定的 <typeparamref name="T"/> 对象的集合。
		/// </summary>
		/// <param name="items">要删除的 <typeparamref name="T"/> 对象的集合。</param>
		/// <param name="safeMode">指定安全操作模式。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public SafeModeResult Remove(IEnumerable<T> items, SafeMode safeMode = null)
		{
			if (safeMode == null) safeMode = SafeMode.False;

			if (items == null) throw new ArgumentNullException("items");

			var database = this.GetDatabase();
			var collectionName = this.GetCollectionName();
			var collection = database.GetCollection(collectionName);

			var query = Query.In("_id", new BsonArray(items.Select(item => item.Id)));

			return collection.Remove(query, safeMode);
		}

		/// <summary>
		/// 更新数据库中的一个 <typeparamref name="T"/> 对象。
		/// </summary>
		/// <param name="item">要更新的 <typeparamref name="T"/> 对象。</param>
		/// <param name="safeMode">指定安全操作模式。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public SafeModeResult Update(T item, SafeMode safeMode = null)
		{
			if (safeMode == null) safeMode = SafeMode.True;

			var database = this.GetDatabase();
			var collectionName = this.GetCollectionName();
			var collection = database.GetCollection(collectionName);

			return collection.Update(Query.EQ("_id", item.Id), UpdateWrapper.Create(item), safeMode);
		}

		/// <summary>
		/// 更新数据库中的多个 <typeparamref name="T"/> 对象。
		/// </summary>
		/// <param name="items">要更新的 <typeparamref name="T"/> 对象的集合。</param>
		/// <param name="safeMode">指定安全操作模式。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public void Update(IEnumerable<T> items, SafeMode safeMode = null) { foreach (var item in items) this.Update(item, safeMode); }

		/// <summary>
		/// 更新或插入数据库中的一个 <typeparamref name="T"/> 对象。
		/// </summary>
		/// <param name="item">要更新的 <typeparamref name="T"/> 对象。</param>
		/// <param name="safeMode">指定安全操作模式。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public SafeModeResult UpdateAndInsert(T item, SafeMode safeMode = null)
		{
			if (safeMode == null) safeMode = SafeMode.True;

			var database = this.GetDatabase();
			var collectionName = this.GetCollectionName();
			var collection = database.GetCollection(collectionName);

			var query = Query.EQ("_id", item.Id);
			var eItem = collection.FindOne(query);

			item.SetId(eItem == null ? this.GetNewId() : eItem["_id"] as TId);

			return collection.Update(query, UpdateWrapper.Create(item), UpdateFlags.Upsert, safeMode);
		}

		/// <summary>
		/// 更新或插入数据库中的多个 <typeparamref name="T"/> 对象。
		/// </summary>
		/// <param name="items">要更新的 <typeparamref name="T"/> 对象的集合。</param>
		/// <param name="safeMode">指定安全操作模式。</param>
		/// <remarks>注意：请使用 <see cref="GetDatabase"/> 连接服务器。</remarks>
		public void UpdateAndInsert(IEnumerable<T> items, SafeMode safeMode = null)
		{
			if (safeMode == null) safeMode = SafeMode.True;

			if (items == null) throw new ArgumentNullException("items");

			var database = this.GetDatabase();
			var collectionName = this.GetCollectionName();
			var collection = database.GetCollection(collectionName);

			foreach (var item in items)
			{
				var query = Query.EQ("_id", item.Id);
				var eItem = collection.FindOne(query);

				item.SetId(eItem == null ? this.GetNewId() : eItem["_id"] as TId);

				collection.Update(query, UpdateWrapper.Create(item), UpdateFlags.Upsert, safeMode);
			}
		}

		/// <summary>
		/// 重建所有索引。
		/// </summary>
		public abstract void RebuildIndex();

		/// <summary>
		/// 删除所有索引。
		/// </summary>
		public CommandResult DropAllIndexes()
		{
			var database = this.GetDatabase();
			var collectionName = this.GetCollectionName();
			var collection = database.GetCollection(collectionName);

			return collection.DropAllIndexes();
		}

		/// <summary>
		/// 删除具有指定名称的索引。
		/// </summary>
		/// <param name="indexName">要将其删除的索引的名称。</param>
		public CommandResult DropIndexByName(string indexName)
		{
			if (indexName == null) throw new ArgumentNullException("indexName");
			if (String.IsNullOrWhiteSpace(indexName)) throw new ArgumentOutOfRangeException("indexName");

			var database = this.GetDatabase();
			var collectionName = this.GetCollectionName();
			var collection = database.GetCollection(collectionName);

			return collection.DropIndexByName(indexName);
		}
	}
}
